/*
 * Copyright 2008 Neil Bacon
 * Distributed under the terms of the Mozilla Public License version 1.1
 * or the GNU General Public License version 3.
 */

package nb.xadd.operator.impl;


import nb.cache.CacheMap;
import nb.util.IntMap;
import nb.xadd.node.Node;
import nb.xadd.node.NodeFactory;
import nb.xadd.node.impl.BooleanDecisionNode;
import nb.xadd.node.impl.RealEqualDecisionNode;
import nb.xadd.node.impl.RealGreaterOrEqualDecisionNode;
import nb.xadd.operator.OperatorFactory;
import nb.xadd.operator.UnaryOperator;

/**
 * Variable substitution.
 */
public final class VariableSubstitution implements UnaryOperator {

    private final NodeFactory nodeFactory;
    private final CacheMap<UnaryOperatorCacheKey, Node> unaryOperatorCache;
    private final IntMap booleanVarMap;
    private final IntMap realVarMap;
    private final int hashCode;
    
    private final UnaryOperator sort;

    protected VariableSubstitution(NodeFactory nodeFactory, OperatorFactory operatorFactory,
            CacheMap<UnaryOperatorCacheKey, Node> unaryOperatorCache,
            IntMap booleanVarMap, IntMap realVarMap) {
        super();
        this.nodeFactory = nodeFactory;
        this.unaryOperatorCache = unaryOperatorCache;
        this.booleanVarMap = booleanVarMap;
        this.realVarMap = realVarMap;
        hashCode = calcHashCode();
        
        sort = operatorFactory.getSort();
    }

    public Node execute(Node n) {
        UnaryOperatorCacheKey key = new UnaryOperatorCacheKey(this, n);
        Node result = unaryOperatorCache.get(key);
        if (result == null) {
            result = sort.execute(walk(n));
            unaryOperatorCache.put(key, result);
        }
        return result;
    }
    
    private int getMappedRealVarIndex(int realVarIndex) {
        return realVarMap.contains(realVarIndex) ? realVarMap.get(realVarIndex) : realVarIndex;
    }

    private int getMappedBooleanVarIndex(int booleanVarIndex) {
        return booleanVarMap.contains(booleanVarIndex) ? booleanVarMap.get(booleanVarIndex) : booleanVarIndex;
    }

    private Node walk(Node n) {
        Node result;
        if (n instanceof RealGreaterOrEqualDecisionNode) {
            RealGreaterOrEqualDecisionNode dn = (RealGreaterOrEqualDecisionNode) n;
            Node trueChild = walk(dn.getTrueChild());
            Node falseChild = walk(dn.getFalseChild());
            result = nodeFactory.getRealGreaterOrEqualDecisionNode(getMappedRealVarIndex(dn.getRealVarIndex()), dn.getTestValue(), trueChild, falseChild);
        } else if (n instanceof RealEqualDecisionNode) {
            RealEqualDecisionNode dn = (RealEqualDecisionNode) n;
            Node trueChild = walk(dn.getTrueChild());
            Node falseChild = walk(dn.getFalseChild());
            result = nodeFactory.getRealEqualDecisionNode(getMappedRealVarIndex(dn.getRealVarIndex()), dn.getTestValue(), trueChild, falseChild);
        } else if (n instanceof BooleanDecisionNode) {
            BooleanDecisionNode dn = (BooleanDecisionNode) n;
            Node trueChild = walk(dn.getTrueChild());
            Node falseChild = walk(dn.getFalseChild());
            result = nodeFactory.getBooleanDecisionNode(getMappedBooleanVarIndex(dn.getBooleanVarIndex()), trueChild, falseChild);
        } else {
            result = n;
        }
//        if (log.isDebugEnabled()) {
//            log.debug("walk: result = " + result);
//        }
        return result;
    }

    @Override
    public int hashCode() {
        return hashCode;
    }

    protected int calcHashCode() {
        return realVarMap.hashCode() ^ booleanVarMap.hashCode();
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj)
            return true;
        if (obj == null || getClass() != obj.getClass())
            return false;
        VariableSubstitution other = (VariableSubstitution) obj;
        return realVarMap.equals(other.realVarMap) && booleanVarMap.equals(other.booleanVarMap);
    }

}
